<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3个meta标签*必须*放在最前面，任何其他内容都*必须*跟随其后！ -->
    <meta name="description" content="多人视频会议开源解决方案">
    <meta name="author" content="https://gitee.com/wesleyOne">
    <link rel="icon" href="/static/images/favicon.ico">
    <title>Shixun视讯 多人视频会议开源解决方案</title>
    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
    <link href="/static/css/bootstrap-theme.min.css" rel="stylesheet">
    <link href="/static/css/bootstrapValidator.min.css" rel="stylesheet">
    <link href="/static/css/bootstrap-switch.min.css" rel="stylesheet">
    <link href="/static/css/simple-openvidu.css" rel="stylesheet">
    <link href="/static/css/cover.css" rel="stylesheet">
</head>
<body>
<div class="site-wrapper">
    <div class="site-wrapper-inner">
        <div class="cover-container">
            <div class="masthead clearfix">
                <div class="inner">
                    <h3 class="masthead-brand">Shixun视讯 多人视频会议开源解决方案</h3>
                    <nav>
                        <ul class="nav masthead-nav" id="member_data" hidden="hidden">
                            <li class="active class="chat_clz""><a id="group_name" href="#" ></a></li>
                            <li class="chat_clz"><input id="start_stop_vidu" type='checkbox' ></li>
                            <li class="chat_clz"><input id="change_vidu_source" type='checkbox' ></li>
                            <li class="chat_clz"><input id="change_record" type='checkbox' ></li>
                            <li class="chat_clz"><input id="change_audio" type='checkbox' ></li>
                            <li class="chat_clz"><input id="change_video" type='checkbox' ></li>
                            <li class="chat_clz"><input id="change_mirror" type='checkbox' ></li>
                            <li ><p id="member_data_name"></p></li>
                            <!--<li ><p id="member_data_id"></p></li>-->
                            <li ><a href="#" onclick="logout()" >退出</a></li>
                        </ul>
                        <form class="navbar-form navbar-right" id="loginForm">
                            <div class="form-group">
                                <input type="text" name="username" placeholder="帐号" class="form-control">
                            </div>
                            <div class="form-group">
                                <input type="password" name="password" placeholder="密码" class="form-control">
                            </div>
                            <button type="button" class="btn btn-success" onclick="loginPost()">登录</button>
                        </form>
                    </nav>
                </div>
            </div>
            <div class="inner cover" id="unlogin_page_cover" >
                <h1>欲穷千里目，更上一层楼</h1>
            </div>
            <div class="inner cover" id="login_page_cover" hidden="hidden">
                <h1>选择群列表。。。</h1>
                <button type="button" class="btn btn-success" onclick="chooseGroup('meetup')">Meetup会议</button>
            </div>
            <div class="inner cover" id="chat_page_cover" hidden="hidden" >
                <div class="row" style="height: 100%">
                    <div class="col-lg-3 col-md-3" style="overflow-y: scroll;height: 100%;" id="video-container">
                    </div>
                    <div class="col-lg-6 col-md-6 " style="height: 100%;" id="main-video">
                        <p class="nickName"></p>
                        <p class="userName"></p>
                        <video autoplay="autoplay" playsinline="true"></video>
                    </div>
                    <div class="col-lg-3 col-md-3" style="height: 100%;">
                        <div class="panel panel-default" style="background-color: #9d9d9d">
                            <div class="panel-body" id="chat_plat_view" style="overflow-y: scroll;height: 460px;">
                                <p id="chat_plat_view_textarea" class="text-left">
                                </p>
                            </div>
                            <div class="panel-footer input-group" id="chat_plat_input">
                                <input type="text" class="form-control" placeholder="文本信息" id="text_msg_data">
                                <span class="input-group-btn">
                                    <button class="btn btn-default" type="button" onclick="sendTextMsg()"><span class="glyphicon glyphicon-send" aria-hidden="true"></span></button>
                                </span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="mastfoot">
                <div class="inner">
                    <p>开源项目地址：<a href="https://gitee.com/wesleyOne/shixun-server" target="_blank">https://gitee.com/wesleyOne/shixun-server</a><p>
                </div>
            </div>
        </div>
    </div>

</div>
<script src="/static/js/jquery-3.3.1.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/bootbox.min.js"></script>
<script src="/static/js/bootstrapValidator.min.js"></script>
<script src="/static/js/bootstrapValidator_zh_CN.js"></script>
<script src="/static/js/bootstrap-switch.min.js"></script>
<script src="/static/js/common.js"></script>
<script src="https://cdn.bootcss.com/sockjs-client/1.0.0/sockjs.min.js"></script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script src="/static/js/openvidu-browser-2.11.0.js"></script>
<script type="text/javascript">

    var mainHost = "";

    <!--用户登录信息-->
    var memberUniqueId;
    var memberNickname;
    var token;
    var role;
    <!--群信息-->
    var groupUniqueId;
    var groupName;
    <!--视频相关-->
    var openViduToken;
    var OV;
    var session;
    var publisher;
    var pulishStatue = false;
    var recordStatue = false;
    <!--发布流配置-->
    var audioSource = undefined; // 音频来源，默认microphone
    var videoSource = undefined; // 视频来源，默认webcam， 屏幕分享screen
    var publishAudio = true;  	// 开启音频 unmuted 未静音
    var publishVideo = true;  	// 开始视频
    var resolution = '640x480';  // 视频分辨率
    var frameRate = 30;			// 视频帧数
    var insertMode = 'APPEND';	// 视频加入目标元素的方式 APPEND拼接
    var mirror = false;       	// 是否镜像显示本地视频

    <!--WebSocket-->
    var stompClient = null;
    var wshost=mainHost;
    var wsport=8888;

    <!--登录相关-->
    var loginValid;
    $(function () {
        loginValid = $('#loginForm').bootstrapValidator({
            message: '非法参数',
            feedbackIcons: {
                valid: 'glyphicon glyphicon-ok',
                invalid: 'glyphicon glyphicon-remove',
                validating: 'glyphicon glyphicon-refresh'
            },
            fields: {
                username: {
                    message: '用户名验证失败',
                    validators: {
                        notEmpty: {
                            message: '账户不能为空'
                        },
                        stringLength: {  //长度限制
                            min: 6,
                            max: 18,
                            message: '用户名长度必须在6到18位之间'
                        }
                    }
                },
                password: {
                    validators: {
                        notEmpty: {
                            message: '密码不能为空'
                        }
                    }
                }
            }
        });

        // switch
        $("#change_record").bootstrapSwitch({
            onText:"录制已开启",
            offText:"录制已关闭",
            onColor:"success",
            offColor:"default",
            size:"small",
            labelWidth:0,
            state:recordStatue,
            onSwitchChange:function(event,state){
                var statename = "关闭";
                if (state){
                    statename = "打开";
                }
                $.ajax({
                    type: "POST",
                    dataType: "json",
                    url:"/ov/record",
                    data:JSON.stringify({
                        groupUniqueId : groupUniqueId,
                        state : state
                    }),
                    contentType: "application/json; charset=utf-8",
                    success:function(result){
                        if (result.code === '00'){

                        } else{
                            bootbox.alert("错误！"+result.message);
                        }
                    },
                    error : function() {
                        bootbox.alert("请求异常");
                    }
                });
            }
        });

        $("#change_mirror").bootstrapSwitch({
            onText:"视频镜像",
            offText:"非镜像",
            onColor:"success",
            offColor:"defult",
            size:"small",
            labelWidth:0,
            state:false,
            onSwitchChange:function(event,state){
                mirror = state;
                republish();
            }
        });

        $("#change_audio").bootstrapSwitch({
            onText:"开启音频",
            offText:"静音",
            onColor:"success",
            offColor:"defult",
            size:"small",
            labelWidth:0,
            state:publishAudio,
            onSwitchChange:function(event,state){
                publishAudio = state;
                republish();
            }
        });

        $("#change_video").bootstrapSwitch({
            onText:"开启视频",
            offText:"黑屏",
            onColor:"success",
            offColor:"defult",
            size:"small",
            labelWidth:0,
            state:publishVideo,
            onSwitchChange:function(event,state){
                publishVideo = state;
                republish();
            }
        });

        $("#change_vidu_source").bootstrapSwitch({
            onText:"分享桌面",
            offText:"摄像头",
            onColor:"info",
            offColor:"success",
            size:"small",
            labelWidth:0,
            state:(videoSource==='screen'),
            onSwitchChange:function(event,state){
                if (state){
                    videoSource = 'screen';
                } else {
                    videoSource = undefined;
                }
                republish();
            }
        });

        $("#start_stop_vidu").bootstrapSwitch({
            onText:"发布中",
            offText:"停止发布",
            onColor:"success",
            offColor:"warning",
            size:"small",
            labelWidth:0,
            state:pulishStatue,
            onSwitchChange:function(event,state){
                pulishStatue = state;
                if (state){
                    republish();
                } else {
                    stopPublish();
                }
            }
        });
    });

    function pageCoverChange(type) {
        if (type === 1) {// 登录
            $('#unlogin_page_cover').hide();
            $('#login_page_cover').show();
            $('#chat_page_cover').hide();
        } else if (type === 2) {// 聊天
            $('#unlogin_page_cover').hide();
            $('#login_page_cover').hide();
            $('#chat_page_cover').show();
        } else { // 其他
            $('#unlogin_page_cover').show();
            $('#login_page_cover').hide();
            $('#chat_page_cover').hide();
        }
    }

    function loginPost() {
        if ( $('#loginForm').data('bootstrapValidator').isValid()){
            var fv = $('#loginForm').serializeObject();
            $.ajax({
                type: "POST",
                dataType: "json",
                url: mainHost+"/login" ,
                data: JSON.stringify(fv),
                contentType: "application/json",
                success: function (result) {
                    if (result.code == '00'){
                        member = result.data;
                        console.log(member);
                        memberUniqueId = member.memberUniqueId;
                        memberNickname = member.memberNickname;
                        token = member.token;
                        role = member.role;
                        $('#member_data_id').text(memberUniqueId);
                        $('#member_data_name').text(memberNickname);
                        $('#loginForm').hide();
                        $('#member_data').show();
                        pageCoverChange(1);
                    } else{
                        commonError(result);
                    }
                },
                error : function() {
                    bootbox.alert("请求异常!");
                }
            });
        }
    }
    // 退出
    function logout() {
        $.ajax({
            type: "POST",
            dataType: "json",
            url: mainHost+"/logout" ,
            data: JSON.stringify({}),
            contentType: "application/json",
            success: function (result) {
                location.reload();
            },
            error : function() {
                blocation.reload();
            }
        });
    }

    function commonError(result) {
        if (result.code === '01' || result.code === '9999') {
            // location.reload();
        } else {
            bootbox.alert("操作失败："+result.message);
        }
    }

    function chooseGroup(guid) {
        $.ajax({
            type: "POST",
            dataType: "json",
            url: mainHost+"/group/join/"+guid ,
            data: JSON.stringify({}),
            contentType: "application/json",
            success: function (result) {
                if (result.code === '00'){
                    groupData = result.data;
                    console.log(groupData);
                    groupUniqueId = groupData.groupUniqueId;
                    groupName = groupData.groupName;
                    pageCoverChange(2);
                    // 默认创建的群
                    $('#group_name').text(groupName);
                    getViduInfo();
                    // connectWs();
                } else{
                    commonError(result);
                }
            },
            error : function() {
                bootbox.alert("请求异常!");
            }
        });
    }

    <!-- openvidu视频相关-->
    // 获取openvidu的token
    function getViduInfo() {
        console.log("groupUniqueId:"+ groupUniqueId);
        if ( groupUniqueId !== undefined) {
            var groupInfo = {groupUniqueId:groupUniqueId};
            $.ajax({
                type: "POST",
                dataType: "json",
                url: mainHost+"/ov/init" ,
                data: JSON.stringify(groupInfo),
                contentType: "application/json",
                success: function (result) {
                    if (result.code === '00'){
                        viduResponse = result.data;
                        openViduToken = viduResponse.openViduToken;
                        setTimeout(startVidu(),200);
                    } else{
                        commonError(result);
                    }
                },
                error : function() {
                    bootbox.alert("请求异常!");
                }
            });
        } else {
            bootbox.alert("未进入群，不能创建视频流");
        }
    }

    // 关闭视频流
    function stopVidu() {
        leaveSession();
    }
    // 启动视频流
    function startVidu() {
        if (openViduToken === null || openViduToken === undefined) {
            bootbox.alert("未获取视频TOKEN，不能开启视频流");
            return;
        }

        console.warn('Request of TOKEN gone WELL (TOKEN:' + openViduToken + ')');
        // --- 1) 生成OpenVidu对象---
        OV = new OpenVidu();
        // --- 2) 初始化OpenVidu会话---
        session = OV.initSession();
        // --- 3) 指定订阅流事件发生时的操作---
        // --- 3.1)在收到的每个新流上
        session.on('streamCreated', (event) => {
            // HTML video标签 将会追加到id为'video-container'的元素
            var subscriber = session.subscribe(event.stream, 'video-container');
        // 将HTML的video标签添加到DOM后
        subscriber.on('videoElementCreated', (event) => {
            // 添加用户信息的html元素在视频上
            console.log("subscriber videoElementCreated");
            appendUserData(event.element, subscriber.stream.connection);
            });
        });
        // --- 3.2)在每个流销毁时
        session.on('streamDestroyed', (event) => {
            // 移除那个流用户的html标签
            console.log("session streamDestroyed");
            removeUserData(event.stream.connection);
        });
        // --- 3.3)监听消息
        session.on('signal', (event) => {
            console.log(event.data); // Message
            console.log(event.from); // Connection object of the sender
            console.log(event.type); // The type of message
            showMsgByOpenvidu(event);
        });

        // --- 4) 通过视频token连接视频服务通过服务端
        var sessionData = {'memberNickname':memberNickname,'memberUniqueId':memberUniqueId};
        session.connect(openViduToken, sessionData).then(() => {
            // --- 5) 设置当前通话的页面布局为了接下来的通讯场景---
            pageCoverChange(2);
            republish();
        }).catch(error => {
            console.warn('连接openvidu服务器失败:', error.code, error.message);
        });
    }

    /**
     * 停止发布视频流
     */
    function stopPublish() {
        if (session!== undefined && publisher !== undefined) {
            var userData = {
                nickName: memberNickname,
                userName: memberUniqueId
            };
            removeUserData(userData);
            session.unpublish(publisher);
        }
    }

    /**
     * 刷新发布配置
     */
    function republish() {
        // 尝试对有发布权限的进行流发布。
        // 即使某人篡改了客户端代码然后发布流，如果当前token没有被OpenVidu Server识别为'PUBLIHSER'角色也不会工作的
        console.log("pulishStatue:"+pulishStatue);
        if (!pulishStatue) {
            return;
        }
        if (isPublisher()) {
            console.log("publisher:"+publisher);
            console.log("publisher!=undefined:"+(publisher !== undefined));
            if (publisher !== undefined) {
                session.unpublish(publisher);
            }
            publisher = OV.initPublisher('video-container', {
                audioSource: audioSource, // 音频来源，默认microphone
                videoSource: videoSource, // 视频来源，默认webcam， 屏幕分享screen
                publishAudio: publishAudio,  	// 开启音频 unmuted 未静音
                publishVideo: publishVideo,  	// 开始视频
                resolution: resolution,  // 视频分辨率
                frameRate: frameRate,			// 视频帧数
                insertMode: insertMode,	// 视频加入目标元素的方式 APPEND拼接
                mirror: mirror       	// 是否镜像显示本地视频
            });
            // --- 7) -指定发布事件发生时的处理--
            // --- 7.1) 当自己的HTML视频添加到DOM后
            publisher.on('videoElementCreated', (event) => {
                // 初始化主视频使用自己的信息
                var userData = {
                    nickName: memberNickname,
                    userName: memberUniqueId
                };
                removeUserData(userData);
                initMainVideo(event.element, userData);
                appendUserData(event.element, userData);
                $(event.element).prop('muted', true); // 设置本地视频静音（在标签里加属性Mute=true）
            });
            session.publish(publisher);
        } else {
            // 没有发布权限时，主视频处显示提示蒙层
            console.warn('你没有发布视频权限');
            initMainVideoThumbnail();
        }

    }

    function sendMsgByOpenvidu(message, type) {
        if(session != undefined) {
            session.signal({
                data: message,  // Any string (optional)
                to: [],                     // Array of Connection objects (optional. Broadcast to everyone if empty)
                type: type             // The type of message (optional)
            }).then(() => {
                console.log('Message successfully sent');
            }).catch(error => {
                    console.error(error);
            });
        }
    }

    function leaveSession() {
        // --- 9) 断开与openvidu服务器连接---
        session.disconnect();
    }
    // 添加连接的用户信息
    function appendUserData(videoElement, connection) {
        var nickName;
        var userName;
        var nodeId;
        if (connection.nickName) { // 是本地视频时
            nickName = connection.nickName;
            userName = connection.userName;
            nodeId = 'main-videodata';
        } else {
            console.log("connection.data:"+connection.data);
            nickName = JSON.parse(connection.data.split('%/%')[0]).memberNickname;
            userName = JSON.parse(connection.data.split('%/%')[0]).memberUniqueId;
            nodeId = connection.connectionId;
        }
        var dataNode = document.createElement('div');
        dataNode.className = "data-node";
        dataNode.id = "data-" + nodeId;
        dataNode.innerHTML = '<p class="nickName">' + nickName + '</p><p class="userName">' + userName + '</p>';
        videoElement.parentNode.insertBefore(dataNode, videoElement.nextSibling);
        // 添加点击替换主视频事件
        addClickListener(videoElement, nickName, userName);
    }
    // 移除某个连接
    function removeUserData(connection) {
        console.log("removeUserData-connection:"+connection);
        if (connection.nickName) {// 本地发布视频
            connection.connectionId = "main-videodata";
        }
        var userNameRemoved = $("#data-" + connection.connectionId);
        // 如果这个用户视频时主标签的则清空主视频的标签信息
        if ($(userNameRemoved).find('p.userName').html() === $('#main-video p.userName').html()) {
            console.log("removeUserData-connection-main");
            cleanMainVideo();
        }
        console.log("removeUserData-connection-id:"+connection.connectionId);
        $("#data-" + connection.connectionId).each(function () {
            $(this).remove();
        });
    }
    // 移除所有用户信息
    function removeAllUserData() {
        $(".data-node").remove();
    }
    // 清空主视频信息
    function cleanMainVideo() {
        $('#main-video video').get(0).srcObject = null;
        $('#main-video p').each(function () {
            $(this).html('');
        });
    }
    // 添加点击切换主视频事件
    function addClickListener(videoElement, nickName, userName) {
        videoElement.addEventListener('click', function () {
            // 获取当前主视频标签
            var mainVideo = $('#main-video video').get(0);
            // 如果主视频的地址不是点击的视频名字，则替换主视频为点击视频，包括视频上的名称和昵称
            if (mainVideo.srcObject !== videoElement.srcObject) {
                $('#main-video').fadeOut("fast", () => {
                    $('#main-video p.nickName').html(nickName);
                    $('#main-video p.userName').html(userName);
                    mainVideo.srcObject = videoElement.srcObject;
                    $('#main-video').fadeIn("fast");
                });
            }
        });
    }
    // 初始化主视频信息
    function initMainVideo(videoElement, userData) {
        $('#main-video video').get(0).srcObject = videoElement.srcObject;
        $('#main-video p.nickName').html(userData.nickName);
        $('#main-video p.userName').html(userData.userName);
        $('#main-video video').prop('muted', true);
    }
    // 当前用户时订阅者时，不能发布视频时的占位图
    function initMainVideoThumbnail() {
        $('#main-video video').css("background", "url('/static/images/subscriber-msg.jpg') round");
    }
    // 初步判断角色
    function isPublisher() {
        // console.log("ROLE:"+role);
        // console.log("ROLE Is publisher? :"+role.includes('publisher'));
        return true;
        // return role.includes('publisher');
    }

    <!--websocket-->
    function setConnected(connected) {
        // document.getElementById('connect').disabled = connected;
        // document.getElementById('disconnect').disabled = !connected;
        // document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
        $('#chat_plat_view_textarea').html();
    }
    // SendUser ***********************************************
    function connectWs() {
        //地址+端点路径，构建websocket链接地址,注意，对应config配置里的addEndpoint
        if (groupUniqueId === undefined) {
            bootbox.alert("未加入会议");
            return;
        }
        console.log('wsurl:' +wshost+ ":"+wsport+'/stomp' + '?token=' + token);
        var socket = new SockJS(wshost+":"+wsport+'/stomp' + '?token=' + token);
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function(frame) {
            setConnected(true);
            console.log('ws连接:' + frame);
            //监听的路径以及回调
            stompClient.subscribe('/topic/'+groupUniqueId, function(response){
                showResponse(response.body);
            });
            stompClient.subscribe('/user/queue/'+groupUniqueId, function(response) {
                showResponse(response.body);
            });
        });
    }
    function disconnect() {
        if (stompClient != null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }
    function sendTextMsg() {
        var textMsg = $('#text_msg_data').val();
        // stompClient.send("/msg/send", {}, JSON.stringify({groupUniqueId:groupUniqueId,type:'SIGN_START',sourcePrincipal:token,massageData:{text:textMsg}}));
        sendMsgByOpenvidu(textMsg,"chat1");
    }

    function showMsgByOpenvidu(event) {
        console.log('========收到消息=========');
        var response = $('#chat_plat_view_textarea');
        var fromNickName = JSON.parse(event.from.data.split('%/%')[0]).memberNickname;
        response.append("<p>"+fromNickName+":  "+event.data+"</p>");
    }

    function showResponse(message) {
        console.log('=================');
        var response = $('#chat_plat_view_textarea');
        console.log(message);
        var jsonMessage = JSON.parse(message);
        console.log(jsonMessage.type);
        console.log(jsonMessage.massageData.text);
        response.html(jsonMessage.massageData.text);
    }
</script>
</body>
</html>
